"""
邢不行™️ 策略分享会
仓位管理实盘框架

版权所有 ©️ 邢不行
微信: xbx6660

本代码仅供个人学习使用，未经授权不得复制、修改或用于商业用途。

Author: 邢不行
"""
import time
from datetime import datetime, timedelta
from pathlib import Path
from typing import List

import pandas as pd
from tqdm import tqdm

from config import utc_offset, bmac_data_path, stable_symbol
from core.model.backtest_config import BacktestConfig
from core.utils.log_kit import logger
from core.utils.path_kit import get_file_path


def check_flags(run_time, flag_paths: List[Path]):
    # 检查 flag 是否都已经 ready
    while True:
        if all(flag_path.exists() for flag_path in flag_paths):
            return True

        # 当前时间是否超过run_time
        if datetime.now() > run_time + timedelta(minutes=5):
            # 如果当前时间超过run_time半小时，表示已经错过当前run_time的下单时间，可能数据中心更新数据失败，没有生成flag文件
            break
        time.sleep(1)

    return False


def check_bmac_update_flag(run_time):
    minute = run_time.minute

    spot_ready_file_path = bmac_data_path / f'{minute}m' / f"spot_dict_{int(run_time.timestamp())}.ready"
    swap_ready_file_path = bmac_data_path / f'{minute}m' / f"swap_dict_{int(run_time.timestamp())}.ready"
    logger.debug(f'🛂 spot_ready_file={spot_ready_file_path}')
    logger.debug(f'🛂 swap_ready_file={swap_ready_file_path}')
    return check_flags(run_time, [spot_ready_file_path, swap_ready_file_path])


def check_bmac_pivot_flag(run_time):
    minute = run_time.minute
    ts = int(run_time.timestamp())
    logger.debug(f'🌐 {run_time}需要的pivot数据准备中...')
    spot_ready_file_path = bmac_data_path / f'{minute}m' / f"market_pivot_spot_{ts}.ready"
    swap_ready_file_path = bmac_data_path / f'{minute}m' / f"market_pivot_swap_{ts}.ready"
    # logger.debug(f'pivot_spot_ready_file={spot_ready_file_path}')
    # logger.debug(f'pivot_swap_ready_file={swap_ready_file_path}')
    return check_flags(run_time, [spot_ready_file_path, swap_ready_file_path])


def read_and_merge_bmac_data(conf: BacktestConfig, df: pd.DataFrame, run_time):
    """
    读取k线数据，并且合并三方数据
    :param conf:  账户配置
    :param df:          k线数据
    :param run_time:   实盘运行时间
    :return:
    """
    if df is None or df.empty:
        return None, None
    symbol = df['symbol'].iloc[-1]  # 获取币种名称
    if symbol.endswith('USDT') and symbol[:-4] in stable_symbol:  # 稳定币不参与选币
        return symbol, None
    if symbol.endswith('USDC') and symbol[:-4] in stable_symbol:  # 稳定币不参与选币
        return symbol, None
    if symbol in conf.black_list:  # 黑名单币种直接跳过
        return symbol, None
    if conf.white_list and symbol not in conf.white_list:  # 不是白名单的币种跳过
        return symbol, None

    # TODO: 优化数据结构，节省掉这里的排序和去重操作
    df.drop_duplicates(subset=['candle_begin_time'], keep='last', inplace=True)  # 去重保留最新的数据
    df.sort_values('candle_begin_time', inplace=True)  # 通过candle_begin_time排序
    df.dropna(subset=['symbol'], inplace=True)

    df['first_candle_time'] = df['first_candle_time'].dt.tz_localize(None)
    df['last_candle_time'] = df['last_candle_time'].dt.tz_localize(None)
    df['candle_begin_time'] = df['candle_begin_time'].dt.tz_localize(None)
    df = df[df['candle_begin_time'] + pd.Timedelta(hours=utc_offset) < run_time]  # 根据run_time过滤一下时间

    # 不能踢掉历史数据不够的情况
    if df.shape[0] < conf.min_kline_num:
        return symbol, None

    # 合并数据  跟回测保持一致
    data_dict, factor_dict = {}, {}
    for cls in conf.strategy_list:
        df, factor_dict, data_dict = cls.after_merge_index(df, symbol, factor_dict, data_dict)

    # 转换成日线数据  跟回测保持一致
    if conf.is_day_period:
        df = trans_period_for_day(df, factor_dict=factor_dict)

    df = df[-conf.get_kline_num():]  # 根据config配置，控制内存中币种的数据，可以节约内存，加快计算速度

    df['symbol_swap'].fillna(value='', inplace=True)
    df['symbol_spot'].fillna(value='', inplace=True)

    # 重置索引并且返回
    return symbol, df.reset_index(drop=True)


def load_bmac_data(symbol_type, run_time, conf: BacktestConfig):
    """
    加载数据
    :param symbol_type: 数据类型
    :param run_time:  实盘的运行时间
    :param conf:  账户配置
    :return:
    """
    hour_offset = f'{run_time.minute}m'
    data_dict = pd.read_pickle(get_file_path(bmac_data_path, hour_offset, f'{symbol_type}_dict.pkl'))

    results = dict()
    for _df in tqdm(data_dict.values(), desc=f'💿 {symbol_type.upper()}数据'):
        symbol, df_candle = read_and_merge_bmac_data(conf, _df, run_time)
        if df_candle is not None:
            results[symbol] = df_candle

    return results


def trans_period_for_day(df, date_col='candle_begin_time', factor_dict=None):
    """
    将数据周期转换为指定的1D周期
    :param df: 原始数据
    :param date_col: 日期列
    :param factor_dict: 转换规则
    :return:
    """
    df.set_index(date_col, inplace=True)
    # 必备字段
    agg_dict = {
        'symbol': 'first',
        'open': 'first',
        'high': 'max',
        'low': 'min',
        'close': 'last',
        'volume': 'sum',
        'quote_volume': 'sum',
        'symbol_type': 'last',
        'tag': 'first',
    }
    if factor_dict:
        agg_dict = dict(agg_dict, **factor_dict)
    df = df.resample('1D').agg(agg_dict)
    df.reset_index(inplace=True)

    return df
